import { EventEmitter, Injectable } from '@angular/core';
import { Graph, Node } from '@antv/x6';
import { GraphConfigService, GraphOptionSetting, PluginSetting } from '../graph-config.service';
import {
  GraphViewportSettings,
  NodeConfig,
  ScenarioConfig
} from '../models';

import { Clipboard } from '@antv/x6-plugin-clipboard';
import { Dnd } from '@antv/x6-plugin-dnd';
import { Export } from "@antv/x6-plugin-export";
import { History } from '@antv/x6-plugin-history';
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { Selection } from '@antv/x6-plugin-selection';
import { Snapline } from '@antv/x6-plugin-snapline';
import { Transform } from '@antv/x6-plugin-transform';
import { Logger } from 'src/service/logger';
import { CellType } from '../enums';
import { MonitorBoardNode } from '../nodes/MonitorBoardNode';
import { PiplineNode } from '../nodes/PiplineNode';
import { RgvNode } from '../nodes/RgvNode';
import { SiteNode } from '../nodes/SiteNode';
import { SrmNode } from '../nodes/SrmNode';
import {
  getMetaFromType,
  getMonitorBoards,
  getPiplines,
  getRgvs,
  getSrms,
  loadScenario,
  registerTypes,
  unregisterTypes,
  updateScenarioNode
} from '../nodes/nodes-util';

export declare const window: {
  __x6_instances__: any[];
} & Window;


@Injectable()
export class ScenarioDesignGraphService {

  nodeSelected: EventEmitter<Node> = new EventEmitter();
  graph!: Graph;
  scenarioId?: string;

  constructor(private configService: GraphConfigService,
    private logger: Logger) { }
  createNode(data: Node.Metadata): Node {
    return this.graph.createNode(data);
  }

  initDnd(container: HTMLElement) {
    return new Dnd({
      target: this.graph,
      scaled: false,
      dndContainer: container,
    });
  }

  getScenario(): ScenarioConfig {
    const nodes = this.graph.getNodes();
    return {
      ...this._nodesToScenario(nodes),
    };
  }

  private _nodesToScenario(nodes: Node<Node.Properties>[]): ScenarioConfig {
    return {
      Lines: getPiplines(nodes),
      Srms: getSrms(nodes),
      Boards: getMonitorBoards(nodes),
      Rgvs: getRgvs(nodes),
    };
  }

  init(container: HTMLElement) {
    this.graph = new Graph({
      ... this.configService.getGraphOptions(this._getOptionSetting()),
      background: {
        image: '/assets/images/logo.png',
        size: '800px',
        position: 'bottom 10px left 10px',
        opacity: 0.02
      },
      container: container,
      autoResize: container,
    });

    window.__x6_instances__ = [];
    window.__x6_instances__.push(this.graph);

    this.graph.on("node:added", ({ node, index, options }) => {
      node.data.cfg.Id = node.id;

      if (node instanceof SiteNode
        || node instanceof SrmNode
        || node instanceof PiplineNode
        || node instanceof RgvNode
        || node instanceof MonitorBoardNode) {
        node.setLogger(this.logger);
        node.bindPositionAndSizeChange();
      }
    });
    this.setPlugins();
    this._graphBindKeys();
  }

  protected getPluginSetting(): Partial<PluginSetting> {
    return {
      selecton: true,
      transform: true,
      keyboard: true,
      clipboard: true,
      history: true
    };
  }
  protected setPlugins() {
    const { graph } = this;
    const setting = this.getPluginSetting();
    // 设置插件
    if (setting.transform) {
      graph
        .use(
          new Transform({
            resizing: true,
            rotating: false,
          }));
    }
    if (setting.selecton) {
      graph.use(
        new Selection({
          movable: false,
          rubberband: true,
          showNodeSelectionBox: true,
        }));
    }
    if (setting.snapline) {
      graph.use(new Snapline());
    }
    if (setting.keyboard) {
      graph.use(new Keyboard());
    }
    if (setting.clipboard) {
      graph.use(new Clipboard({
        useLocalStorage: true
      }));
    }
    if (setting.history) {
      graph.use(new History());
    }
    if (setting.export) {
      graph.use(new Export());
    }
  }

  private _getOptionSetting(): Partial<GraphOptionSetting> {
    return {
      background: false,
      grid: true,
      panning: true,
      mousewheel: true,
      embedding: true,
      highlighting: true,
    };
  }

  _graphBindKeys() {
    const { graph } = this;
    // #region 快捷键与事件
    graph.bindKey(['meta+c', 'ctrl+c'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.copy(cells);
      }
      return false;
    });
    graph.bindKey(['meta+x', 'ctrl+x'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.cut(cells);
      }
      return false;
    });
    graph.bindKey(['meta+v', 'ctrl+v'], () => {
      if (!graph.isClipboardEmpty()) {
        const cells = graph.paste({ offset: 32 });
        graph.cleanSelection();
        graph.select(cells);
      }
      return false;
    });

    // undo redo
    graph.bindKey(['meta+z', 'ctrl+z'], () => {
      if (graph.canUndo()) {
        graph.undo();
      }
      return false;
    });
    graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      if (graph.canRedo()) {
        graph.redo();
      }
      return false;
    });

    // select all
    graph.bindKey(['meta+a', 'ctrl+a'], () => {
      const nodes = graph.getNodes();
      if (nodes) {
        graph.select(nodes);
      }
    });

    // delete
    graph.bindKey(['delete'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.removeCells(cells);
      }
    });
    graph.on("node:unselected", ({ node }) => {
      this.nodeSelected.emit(undefined);
    });
    graph.on("node:dblclick", ({ node }) => {
      this.nodeSelected.emit(node);
    });

    graph.on('node:change:parent', ({ node }) => {
      const parent = node.getParent();
      if (parent) {
        node.zIndex = parent.zIndex! + 1;
      }
    });

    graph.on('scale', ({ sx, sy, ox, oy }) => {
      if (this.scenarioId) {
        const key = `View${this.scenarioId}`;
        const json = localStorage.getItem(key);
        const settings: GraphViewportSettings = {};
        settings.scale = { sx, sy, ox, oy };
        if (json) {
          const { translate } = JSON.parse(json) as GraphViewportSettings;
          settings.translate = translate;
        }
        localStorage.setItem(key, JSON.stringify(settings));
      }
    });

    graph.on('translate', ({ tx, ty }) => {
      if (this.scenarioId) {
        const key = `View${this.scenarioId}`;
        const json = localStorage.getItem(key);
        const settings: GraphViewportSettings = {};
        settings.translate = { tx, ty };
        if (json) {
          const { scale } = JSON.parse(json) as GraphViewportSettings;
          settings.scale = scale;
        }
        localStorage.setItem(key, JSON.stringify(settings));
      }
    });
  }

  loadScenario(cfg: ScenarioConfig) {
    this.scenarioId = cfg.Id;
    loadScenario(this.graph, cfg);
    this._loadViewport();
  }

  getMetaFromType(type: CellType): Node.Metadata {
    return getMetaFromType(type);
  }

  registerTypes() {
    registerTypes();
  }

  unregisterTypes() {
    unregisterTypes();
  }

  updateScenarioNode(item: NodeConfig) {
    const node = this.graph.getNodes().find(a => a.id === item.Id);
    if (node) {
      updateScenarioNode(item, node);
    }
  }

  private _loadViewport() {
    if (this.scenarioId) {
      const key = `View${this.scenarioId}`;
      const json = localStorage.getItem(key);
      if (json) {
        const { scale, translate } = JSON.parse(json) as GraphViewportSettings;
        const { graph } = this;
        if (scale) {
          graph.scale(scale.sx, scale.sy, scale.ox, scale.oy);
        }
        if (translate) {
          graph.translate(translate.tx, translate.ty);
        }
      }
    }
  }

  clearViewport() {
    this.graph.scale(1, 1, 0, 0);
    this.graph.translate(0, 0);
    if (this.scenarioId) {
      const key = `View${this.scenarioId}`;
      localStorage.removeItem(key);
    }
  }
}

